"""This module defines the class of main window for the program"""

import os
import copy
import webbrowser

from PyQt4 import QtGui

from form_view import *
from class_items import *
from util_background import *
from util_actions import *


class RMainWindow(QtGui.QMainWindow):
    """
    This class includes a title bar, a menu bar, a status bar, and the image scene
    """
    def __init__(self, app, fileToOpen):
        super(RMainWindow, self).__init__()        
        self.programName = 'ccSeek'
        self.license = 'General Public License version 3'
        self.author = 'Roben Tan'
        self.version = '1.0'
        self.organization = 'Washington State University'
        self.contact = 'yunbing.tan@email.wsu.edu'
        self.weblink = 'https://code.google.com/p/ccseek-protein-alignment/'
        pixmap = QtGui.QPixmap('./icon.ico')
        if not pixmap.isNull():
            self.setWindowIcon(QtGui.QIcon(pixmap))

        if fileToOpen:
            self.setWindowTitle('%s for file: %s' % 
                                (self.programName, fileToOpen))
        else:
            self.setWindowTitle(self.programName)
        
        self.setGeometry(100, 100, 800, 600)
        
        self.initSettings()
        self.initInterface()
        self.openInitialFile(fileToOpen)
        self.application = app
    
    def initSettings(self):
        """Setup the initial and background settings"""
        
        # read settings from saved files or internal constants
        self.allSettingsDefault = getSettingsDefault()
        self.allSettingsRecent = getSettingsRecent()
        
        self.aaPropertiesDefault = self.allSettingsDefault['aa properties']
        self.aaSettingsDefault = self.allSettingsDefault['property type definition']
        self.propertyColorsDefault = self.allSettingsDefault['property color mapping']
        
        if self.allSettingsRecent:
            self.aaPropertiesRecent = self.allSettingsRecent['aa properties']
            self.aaSettingsRecent = self.allSettingsRecent['property type definition']
            self.propertyColorsRecent = self.allSettingsRecent['property color mapping']
        else:
            self.aaPropertiesRecent = None
            self.aaSettingsRecent = None
            self.propertyColorsRecent = None
        
        self.plotParametersDefault = getPlotParametersDefault()
        self.shadeColorsRecent = getShadeColorsRecent()
        
        # assign the instance attributes for current use
        self.aaProperties = copy.deepcopy(self.getAAProperties())
        self.aaSettings = copy.deepcopy(self.getAASettings())
        self.propertyColors = copy.deepcopy(self.getPropertyColors())
        self.plotParameters = copy.deepcopy(self.plotParametersDefault)
        
        # determine property category for each amino acid
        # this is based on the property value and the category definition
        self.aaPropertyTypes = self.determinePropertyType()
        
        self.seqData = None
        self.seqDataDefault = None
        self.seqDataNoNewGaps = None
        self.propertyCurrent = 'identity'
        
        self.currentOpenFile = None        
        self.currentViewSaved = False
        self.currentViewSaveFile = None        
        self.currentAlignmentSaved = False
        self.currentAlignmentSaveFile = None        
        self.currentSettingsSaved = False
        self.currentSettingsSaveFile = None
        
        self.viewScaleX = 1
        self.viewScaleY = 1
        
        self.itemMoved = False
        self.undoStack = QtGui.QUndoStack()
        
        self.defaultProperties = set(['identity', 'size', 'charge', 'polarity', 
                                            'hydropathy'])

    def initInterface(self):
        """Configure and setup the GUI interface"""
        
        self.centralWidget = RWidget(self)
        self.setCentralWidget(self.centralWidget)
        self.view = self.centralWidget.view
        self.scene = self.view.scene
        
        menubar = self.menuBar()
        self.fileMenu = menubar.addMenu('&File')
        self.editMenu = menubar.addMenu('&Edit')
        self.settingMenu = menubar.addMenu('&Settings')
        self.dataMenu = menubar.addMenu('&Data')
        self.viewMenu = menubar.addMenu('&View')
        self.aboutMenu = menubar.addMenu('&About')
        
        self.statusBar()
        
        self.addFileActions()
        self.addEditActions()
        self.addSettingActions()
        self.addDataActions()
        self.addViewActions()
        self.addAboutActions()

    def openInitialFile(self, fileToOpen): 
        """
        If there is a file to open when the window is opened, 
        this function will be called. Correspondingly, 
        the alignment image will be drew using the default property
        """
        if fileToOpen:
            self.seqDataDefault = readSequenceFile(fileToOpen)
            self.currentOpenFile = fileToOpen
            
            if self.seqDataDefault:
                self.seqData = copy.deepcopy(self.seqDataDefault)
                self.seqDataNoNewGaps = copy.deepcopy(self.seqDataDefault)
                self.view.scene.clear()
                self.view.drawView(self)
                self.view.drawAlignmentLine(self)
                
                self.saveViewAction.setEnabled(True)
                self.saveViewAsAction.setEnabled(True)
                self.saveAlignmentAction.setEnabled(True)
                self.saveAlignmentAsAction.setEnabled(True)
                self.saveSettingsAction.setEnabled(True)
                self.saveSettingsAsAction.setEnabled(True)
                self.printViewAction.setEnabled(True)
                self.printSettingsAction.setEnabled(True)
                self.selectAllAction.setEnabled(True)
                self.addAlignLineAction.setEnabled(True)
                self.removeEditedGapsAction.setEnabled(True)
                self.restoreInitialAlignAction.setEnabled(True)
                self.zoomViewAction.setEnabled(True)
                self.lineNumViewAction.setEnabled(True)
                self.colNumViewAction.setEnabled(True)
    
    def getAAProperties(self):
        """
        Get the most recent properties first if possible, 
        then use default settings if necessary
        """
        if self.aaPropertiesRecent:
            aaProperties = self.aaPropertiesRecent 
        else:
            aaProperties = self.aaPropertiesDefault
        
        return aaProperties
    
    def getAASettings(self):
        """
        Get the most recent category settings first if possible, 
        then use default settings if necessary
        """
        if self.aaSettingsRecent:
            aaSettings = self.aaSettingsRecent
        else:
            aaSettings = self.aaSettingsDefault
        
        return aaSettings
    
    def getPropertyColors(self):
        """
        Get the most recent color settings first if possible, 
        then use default settings if necessary
        """
        if self.propertyColorsRecent:
            propertyColors = self.propertyColorsRecent
        else:
            propertyColors = self.propertyColorsDefault
        
        return propertyColors
    
    def determinePropertyType(self):
        """
        Determine the property type (category) for each property 
        for each amino acid
        """
        aaPropertyTypes = {}
        for aa in self.aaProperties.keys():
            aaPropertyTypes[aa] = {}
            
            for property in self.aaSettings.keys():
                propertyVal = self.aaProperties[aa][property]
                
                for propertyType in self.aaSettings[property]:
                    propertyValRange = self.aaSettings[property][propertyType]
                    
                    if propertyVal >= propertyValRange[0] and \
                                    propertyVal < propertyValRange[1]:
                        aaPropertyTypes[aa][property] = propertyType
                        break                    
                else:
                    aaPropertyTypes[aa][property] = 'undefined'        
        return aaPropertyTypes
    
    def addFileActions(self):
        """Add actions to File menu"""
        
        self.openAction = QtGui.QAction('&Open', self)
        self.openAction.setShortcut('Ctrl+O')
        self.openAction.setStatusTip('Open sequence file')
        self.openAction.triggered.connect(self.openActionImpl)
        
        self.saveViewAction = QtGui.QAction('&Save View', self)
        self.saveViewAction.setStatusTip('Save current alignment image')
        self.saveViewAction.triggered.connect(self.saveViewActionImpl)
        
        self.saveViewAsAction = QtGui.QAction('Save View &as', self)
        self.saveViewAsAction.setStatusTip(
                    'Save current alignment image to another file')
        self.saveViewAsAction.triggered.connect(self.saveViewAsActionImpl)
        
        self.saveAlignmentAction = QtGui.QAction('Save A&lignment', self)
        self.saveAlignmentAction.setShortcut('Ctrl+S')
        self.saveAlignmentAction.setStatusTip(
                                                'Save current alignment sequences')
        self.saveAlignmentAction.triggered.connect(self.saveAlignmentActionImpl)
        
        self.saveAlignmentAsAction = QtGui.QAction('Save Ali&gnment as', self)
        self.saveAlignmentAsAction.setStatusTip(
                        'Save current alignment sequences to another file')
        self.saveAlignmentAsAction.triggered.connect(
                                                    self.saveAlignmentAsActionImpl)
        
        self.saveSettingsAction = QtGui.QAction('Save Se&ttings', self)
        self.saveSettingsAction.setStatusTip('Save current settings')
        self.saveSettingsAction.triggered.connect(self.saveSettingsActionImpl)
        
        self.saveSettingsAsAction = QtGui.QAction('Save Sett&ings as', self)
        self.saveSettingsAsAction.setStatusTip(
                                        'Save current settings to another file')
        self.saveSettingsAsAction.triggered.connect(
                                                    self.saveSettingsAsActionImpl)
        
        self.printViewAction = QtGui.QAction('&Print View', self)
        self.printViewAction.setStatusTip('Print current alignment')
        self.printViewAction.triggered.connect(self.printViewActionImpl)
        
        self.printSettingsAction = QtGui.QAction('P&rint Settings', self)
        self.printSettingsAction.setStatusTip('Print current settings')
        self.printSettingsAction.triggered.connect(
                                                   self.printSettingsActionImpl)
        
        self.exitAction = QtGui.QAction('E&xit', self)
        self.exitAction.setShortcut('Ctrl+Q')
        self.exitAction.setStatusTip('Exit the application')
        self.exitAction.triggered.connect(self.exitActionImpl)
        
        if not self.scene.items():
            self.saveViewAction.setEnabled(False)
            self.saveViewAsAction.setEnabled(False)
            self.saveAlignmentAction.setEnabled(False)
            self.saveAlignmentAsAction.setEnabled(False)
            self.saveSettingsAction.setEnabled(False)
            self.saveSettingsAsAction.setEnabled(False)
            self.printViewAction.setEnabled(False)
            self.printSettingsAction.setEnabled(False)

        self.fileMenu.addAction(self.openAction)
        self.fileMenu.addAction(self.saveAlignmentAction)
        self.fileMenu.addAction(self.saveAlignmentAsAction)
        self.fileMenu.addAction(self.saveViewAction)
        self.fileMenu.addAction(self.saveViewAsAction)
        self.fileMenu.addAction(self.saveSettingsAction)
        self.fileMenu.addAction(self.saveSettingsAsAction)
        self.fileMenu.addAction(self.printViewAction)
        self.fileMenu.addAction(self.printSettingsAction)
        self.fileMenu.addAction(self.exitAction)
    
    def addEditActions(self):
        """Add actions to Edit menu"""
        
        self.selectAllAction = QtGui.QAction('&Select All', self)
        self.selectAllAction.setShortcut('Ctrl+A')
        self.selectAllAction.setStatusTip('Select all alignment units')
        self.selectAllAction.triggered.connect(self.selectAllActionImpl)
        if not self.seqData:
            self.selectAllAction.setEnabled(False)
        
        self.undoAction = QtGui.QAction('&Undo', self)
        self.undoAction.setShortcut('Ctrl+Z')
        self.undoAction.setStatusTip('Undo the last step in record')
        self.undoAction.triggered.connect(self.undoActionImpl)
        self.undoAction.setEnabled(False)
        
        self.redoAction = QtGui.QAction('&Redo', self)
        self.redoAction.setShortcut('Ctrl+Y')
        self.redoAction.setStatusTip('Redo the last step in record')
        self.redoAction.triggered.connect(self.redoActionImpl)
        self.redoAction.setEnabled(False)
        
        self.addAlignLineAction = QtGui.QAction('Add Alignment Line', self)
        self.addAlignLineAction.setShortcut('Ctrl+L')
        self.addAlignLineAction.setStatusTip('Add an alignment line')
        self.addAlignLineAction.triggered.connect(self.addAlignLineActionImpl)
        if not self.seqData:
            self.addAlignLineAction.setEnabled(False)
        
        self.removeAlignLineAction = QtGui.QAction('Remove Alignment Line', self)
        self.removeAlignLineAction.setShortcut('Ctrl+Shift+L')
        self.removeAlignLineAction.setStatusTip('Remove an alignment line')
        self.removeAlignLineAction.triggered.connect(self.removeAlignLineActionImpl)
        self.removeAlignLineAction.setEnabled(False)

        self.addRegionShadeAction = QtGui.QAction('Add Region Shade', self)
        self.addRegionShadeAction.setShortcut('Ctrl+R')
        self.addRegionShadeAction.setStatusTip('Add shade for the region at point')
        self.addRegionShadeAction.triggered.connect(self.addRegionShadeActionImpl)
        self.addRegionShadeAction.setEnabled(False)
        
        self.removeRegionShadeAction = QtGui.QAction('Remove Region Shade', self)
        self.removeRegionShadeAction.setShortcut('Ctrl+Shift+R')
        self.removeRegionShadeAction.setStatusTip('Remove shade for the region at point')
        self.removeRegionShadeAction.triggered.connect(self.removeRegionShadeActionImpl)
        self.removeRegionShadeAction.setEnabled(False)
        
        self.removeEditedGapsAction = QtGui.QAction('Remove Edited &Gaps', self)
        self.removeEditedGapsAction.setStatusTip('Remove all newly added gaps from the view')
        self.removeEditedGapsAction.triggered.connect(self.removeEditedGapsActionImpl)
        if not self.seqData:
            self.removeEditedGapsAction.setEnabled(False)
        
        self.deleteTruncateAction = QtGui.QAction('&Delete/Truncate', self)
        self.deleteTruncateAction.setShortcut('Shift+Del')
        self.deleteTruncateAction.setStatusTip('Delete or truncate current sequence from view')
        self.deleteTruncateAction.triggered.connect(self.deleteTruncateActionImpl)
        self.deleteTruncateAction.setEnabled(False)
        
        self.restoreInitialAlignAction = QtGui.QAction('Restore Initial &Alignment', self)
        self.restoreInitialAlignAction.setStatusTip('Restore to the initial alignment '
                        'as read from input file')
        self.restoreInitialAlignAction.triggered.connect(self.restoreInitialAlignActionImpl)
        if not self.seqData:
            self.restoreInitialAlignAction.setEnabled(False)
        
        self.moveRightAction = QtGui.QAction('Move Right', self)
        self.moveRightAction.setShortcut('Ctrl+Alt+R')
        self.moveRightAction.setStatusTip('Move the selected units right')
        self.moveRightAction.triggered.connect(self.moveRightActionImpl)
        self.moveRightAction.setEnabled(False)
        
        self.moveLeftAction = QtGui.QAction('Move Left', self)
        self.moveLeftAction.setShortcut('Ctrl+Alt+L')
        self.moveLeftAction.setStatusTip('Move the selected units left')
        self.moveLeftAction.triggered.connect(self.moveLeftActionImpl)
        self.moveLeftAction.setEnabled(False)
        
        self.moveUpAction = QtGui.QAction('Move Up', self)
        self.moveUpAction.setShortcut('Ctrl+Alt+U')
        self.moveUpAction.setStatusTip('Move the selected sequence up')
        self.moveUpAction.triggered.connect(self.moveUpActionImpl)
        self.moveUpAction.setEnabled(False)
        
        self.moveDownAction = QtGui.QAction('Move Down', self)
        self.moveDownAction.setShortcut('Ctrl+Alt+D')
        self.moveDownAction.setStatusTip('Move the selected sequence down')
        self.moveDownAction.triggered.connect(self.moveDownActionImpl)
        self.moveDownAction.setEnabled(False)
        
        self.editMenu.addAction(self.selectAllAction)
        self.editMenu.addSeparator()
        self.editMenu.addAction(self.undoAction)
        self.editMenu.addAction(self.redoAction)
        self.editMenu.addSeparator()
        self.editMenu.addAction(self.moveRightAction)
        self.editMenu.addAction(self.moveLeftAction)
        self.editMenu.addAction(self.moveUpAction)
        self.editMenu.addAction(self.moveDownAction)
        self.editMenu.addSeparator()
        self.editMenu.addAction(self.deleteTruncateAction)
        self.editMenu.addAction(self.removeEditedGapsAction)
        self.editMenu.addAction(self.restoreInitialAlignAction)
        self.editMenu.addSeparator()
        self.editMenu.addAction(self.addAlignLineAction)
        self.editMenu.addAction(self.removeAlignLineAction)
        self.editMenu.addAction(self.addRegionShadeAction)
        self.editMenu.addAction(self.removeRegionShadeAction)
    
    def addSettingActions(self):
        """Add actions to Setting menu"""
        
        self.resetSettingAction = QtGui.QAction('&Reset to Default', self)
        self.resetSettingAction.setStatusTip(
                                    'Reset the settings to default values')
        self.resetSettingAction.triggered.connect(self.resetSettingActionImpl)
        
        self.saveDefaultSettingAction = QtGui.QAction('Save as &Default', self)
        self.saveDefaultSettingAction.setStatusTip(
                                            'Save current settings as default')
        self.saveDefaultSettingAction.triggered.connect(
                                                        self.saveDefaultSettingActionImpl)
        
        self.sizeSettingAction = QtGui.QAction('&Size', self)
        self.sizeSettingAction.setStatusTip(
                        'Configure the settings related to amino acid size')
        self.sizeSettingAction.triggered.connect(self.sizeSettingActionImpl)
        
        self.chargeSettingAction = QtGui.QAction('&Charge', self)
        self.chargeSettingAction.setStatusTip(
                        'Configure the settings related to amino acid charge')
        self.chargeSettingAction.triggered.connect(self.chargeSettingActionImpl)
        
        self.polaritySettingAction = QtGui.QAction('&Polarity', self)
        self.polaritySettingAction.setStatusTip(
                    'Configure the settings related to amino acid polarity')
        self.polaritySettingAction.triggered.connect(self.polaritySettingActionImpl)
        
        self.hydroSettingAction = QtGui.QAction('&Hydropathy', self)
        self.hydroSettingAction.setStatusTip(
                    'Configure the settings related to amino acid hydropathy')
        self.hydroSettingAction.triggered.connect(self.hydroSettingActionImpl)
                
        self.moreSettingAction = QtGui.QAction('More Properties ...', self)
        self.moreSettingAction.setStatusTip('Configure customized settings')
        self.moreSettingAction.triggered.connect(self.moreSettingActionImpl)
        
        self.settingMenu.addAction(self.resetSettingAction)
        self.settingMenu.addAction(self.saveDefaultSettingAction)
        self.settingMenu.addSeparator()
        self.settingMenu.addAction(self.sizeSettingAction)
        self.settingMenu.addAction(self.chargeSettingAction)
        self.settingMenu.addAction(self.polaritySettingAction)
        self.settingMenu.addAction(self.hydroSettingAction)
        self.settingMenu.addSeparator()
        self.settingMenu.addAction(self.moreSettingAction)
    
    def addDataActions(self):
        """Add actions to Data menu"""
        
        self.idDataAction = QtGui.QAction('&ID', self)
        self.idDataAction.setStatusTip(
                        "Retrieve the ID's for currently aligned sequences")
        self.idDataAction.triggered.connect(self.idDataActionImpl)
        
        self.seqDataAction = QtGui.QAction('&Sequence', self)
        self.seqDataAction.setStatusTip(
                        'Retrieve the sequences for current alignment')
        self.seqDataAction.triggered.connect(self.seqDataActionImpl)
        
        self.lookupDataAction = QtGui.QAction('&Lookup', self)
        self.lookupDataAction.setStatusTip(
                        'Look up the properties for your input amino acid')
        self.lookupDataAction.triggered.connect(self.lookupDataActionImpl)
        
        self.dataMenu.addAction(self.idDataAction)
        self.dataMenu.addAction(self.seqDataAction)
        self.dataMenu.addAction(self.lookupDataAction)
    
    def addViewActions(self):
        """Add actions to View menu"""
        
        self.refreshViewAction = QtGui.QAction('&Refresh', self)
        self.refreshViewAction.setStatusTip('Refresh the alignment image')
        self.refreshViewAction.triggered.connect(self.refreshViewActionImpl)
        
        self.propertyViewActionMenu = QtGui.QMenu()
        self.propertyViewActionGroup = QtGui.QActionGroup(self)
        
        self.identityViewAction = QtGui.QAction('&Identity', self)
        self.identityViewAction.setCheckable(True)
        self.identityViewAction.setChecked(True)
        
        self.sizeViewAction = QtGui.QAction('&Size', self)
        self.sizeViewAction.setCheckable(True)
        
        self.chargeViewAction = QtGui.QAction('&Charge', self)
        self.chargeViewAction.setCheckable(True)
        
        self.polarityViewAction = QtGui.QAction('&Polarity', self)
        self.polarityViewAction.setCheckable(True)
        
        self.hydroViewAction = QtGui.QAction('&Hydropathy', self)
        self.hydroViewAction.setCheckable(True)
        
        self.moreViewAction = QtGui.QAction('More Properties ...', self)
        self.moreViewAction.setCheckable(True)
        allProperties = set(self.aaSettings.keys())
        customizedProperties = allProperties - set(self.defaultProperties)
        if not customizedProperties:
            self.moreViewAction.setEnabled(False)
        
        self.propertyViewActionGroup.addAction(self.identityViewAction)
        self.propertyViewActionGroup.addAction(self.sizeViewAction)
        self.propertyViewActionGroup.addAction(self.chargeViewAction)
        self.propertyViewActionGroup.addAction(self.polarityViewAction)
        self.propertyViewActionGroup.addAction(self.hydroViewAction)
        self.propertyViewActionGroup.addAction(self.moreViewAction)
        self.propertyViewActionGroup.triggered.connect(
                                                       self.propertyViewActionImpl)
        
        self.propertyViewActionMenu.addAction(self.identityViewAction)
        self.propertyViewActionMenu.addAction(self.sizeViewAction)
        self.propertyViewActionMenu.addAction(self.chargeViewAction)
        self.propertyViewActionMenu.addAction(self.polarityViewAction)
        self.propertyViewActionMenu.addAction(self.hydroViewAction)
        self.propertyViewActionMenu.addAction(self.moreViewAction)
        
        self.propertyViewAction = QtGui.QAction('&Property', self)
        self.propertyViewAction.setStatusTip(
                                'Choose which property to align sequences')
        self.propertyViewAction.setMenu(self.propertyViewActionMenu)
        
        self.colorViewAction = QtGui.QAction('&Color', self)
        self.colorViewAction.setStatusTip(
                        'Choose which color to show each property value')
        self.colorViewAction.triggered.connect(self.colorViewActionImpl)
        
        self.zoomInAction = QtGui.QAction('&In', self)
        self.zoomInAction.setShortcut('Ctrl+=')
        self.zoomInAction.triggered.connect(self.zoomInActionImpl)
        
        self.zoomOutAction = QtGui.QAction('&Out', self)
        self.zoomOutAction.setShortcut('Ctrl+-')
        self.zoomOutAction.triggered.connect(self.zoomOutActionImpl)
        
        self.zoomDefaultAction = QtGui.QAction('&Default', self)
        self.zoomDefaultAction.setShortcut('Ctrl+0')
        self.zoomDefaultAction.triggered.connect(self.zoomDefaultActionImpl)
        
        self.zoomViewActionMenu = QtGui.QMenu()
        self.zoomViewActionMenu.addAction(self.zoomInAction)
        self.zoomViewActionMenu.addAction(self.zoomOutAction)
        self.zoomViewActionMenu.addAction(self.zoomDefaultAction)
        
        self.zoomViewAction = QtGui.QAction('&Zoom', self)
        self.zoomViewAction.setStatusTip('Zoom in or out the alignment image')
        self.zoomViewAction.setMenu(self.zoomViewActionMenu)
        if not self.seqData:
            self.zoomViewAction.setEnabled(False)
        
        self.lineNumViewAction = QtGui.QAction('&Line Number', self)
        self.lineNumViewAction.setStatusTip('Show or hide line numbers')
        self.lineNumViewAction.setCheckable(True)
        self.lineNumViewAction.setChecked(True)
        self.lineNumViewAction.toggled.connect(self.lineNumViewActionImpl)
        if not self.seqData:
            self.lineNumViewAction.setEnabled(False)
        
        self.colNumViewAction = QtGui.QAction('Column &Number', self)
        self.colNumViewAction.setStatusTip('Show or hide column numbers')
        self.colNumViewAction.setCheckable(True)
        self.colNumViewAction.setChecked(True)
        self.colNumViewAction.toggled.connect(self.colNumViewActionImpl)
        if not self.seqData:
            self.colNumViewAction.setEnabled(False)
        
        self.viewMenu.addAction(self.refreshViewAction)
        self.viewMenu.addAction(self.propertyViewAction)
        self.viewMenu.addAction(self.colorViewAction)
        self.viewMenu.addAction(self.zoomViewAction)
        self.viewMenu.addAction(self.lineNumViewAction)
        self.viewMenu.addAction(self.colNumViewAction)
    
    def addAboutActions(self):
        """Add actions to Help menu"""
        
        self.aboutAboutAction = QtGui.QAction('&About', self)
        self.aboutAboutAction.setStatusTip(
                                    'Show the contact information of authors')
        self.aboutAboutAction.triggered.connect(self.aboutAboutActionImpl)
        
        self.manualAboutAction = QtGui.QAction('&Manual', self)
        self.manualAboutAction.setStatusTip('Shoe the application manual')
        self.manualAboutAction.triggered.connect(self.manualAboutActionImpl)
        
        self.aboutMenu.addAction(self.aboutAboutAction)
        self.aboutMenu.addAction(self.manualAboutAction)
    
    def openActionImpl(self):
        """Implementation of action: File -> Open"""
        
        if self.currentOpenFile:
            direct = self.currentOpenFile
        else:
            direct = os.getcwd()
        srcFileName = QtGui.QFileDialog.getOpenFileName(self, 
                                                        'Open file', direct)
        
        if srcFileName and self.seqData:
            mainWindow = RMainWindow(self.application, srcFileName)
            self.application.windowList.append(mainWindow)
            mainWindow.show()
            
        elif srcFileName:
            self.seqDataDefault = readSequenceFile(srcFileName)
            
            if self.seqDataDefault:
                self.currentOpenFile = srcFileName
                self.setWindowTitle('%s for file: %s' % 
                                    (self.programName, srcFileName))

                self.seqData = copy.deepcopy(self.seqDataDefault)
                self.seqDataNoNewGaps = copy.deepcopy(self.seqDataDefault)
                self.view.scene.clear()
                self.view.drawView(self)
                self.view.drawAlignmentLine(self)
                
                self.saveViewAction.setEnabled(True)
                self.saveViewAsAction.setEnabled(True)
                self.saveAlignmentAction.setEnabled(True)
                self.saveAlignmentAsAction.setEnabled(True)
                self.saveSettingsAction.setEnabled(True)
                self.saveSettingsAsAction.setEnabled(True)
                self.printViewAction.setEnabled(True)
                self.printSettingsAction.setEnabled(True)
                self.selectAllAction.setEnabled(True)
                self.addAlignLineAction.setEnabled(True)
                self.removeEditedGapsAction.setEnabled(True)
                self.restoreInitialAlignAction.setEnabled(True)
                self.zoomViewAction.setEnabled(True)
                self.lineNumViewAction.setEnabled(True)
                self.colNumViewAction.setEnabled(True)
    
    def saveViewActionImpl(self):
        """Implementation of action: File -> Save View"""
        
        if self.currentViewSaveFile:
            saveViewToFile(self.scene, self.currentViewSaveFile)
            self.currentViewSaved = True
            
        else:       # save for the first time, same as "Save as"
            self.saveViewAsActionImpl()
    
    def saveViewAsActionImpl(self):
        """Implementation of action: File -> Save View as"""
        
        if self.currentViewSaveFile:
            direct = self.currentViewSaveFile
        else:
            direct = os.getcwd()

        acceptedFileTypes = ['bmp', 'gif', 'jpg', 'pdf', 
                                        'png', 'ps', 'tiff']
        acceptedFileNames = ['BMP', 'GIF', 'JPG/JPEG', 'PDF', 
                                        'PNG', 'PS', 'TIFF']
        acceptedFilePairs = zip(acceptedFileNames, acceptedFileTypes)
        
        filter = ';;'.join([filePair[0]+'(*.'+filePair[1]+')' 
                                    for filePair in acceptedFilePairs])
        saveFileName = QtGui.QFileDialog.getSaveFileName(self, 
                                    'Save as file', direct, filter)
        
        extension = saveFileName.split('.')[-1]
        if extension in acceptedFileTypes:
            saveViewToFile(self.scene, saveFileName)
            self.currentViewSaved = True
            self.currentViewSaveFile = saveFileName
            
        elif extension:
            messageBox = QtGui.QErrorMessage()
            messageBox.setWindowTitle('Error')
            messageBox.showMessage('Image format not supported.')
            messageBox.exec_()
    
    def saveAlignmentActionImpl(self):
        """Implementation of action: File -> Save Alignment"""
        
        if self.seqData:
            if self.currentAlignmentSaveFile:
                saveAlignmentToFile(self.seqData, 
                                    self.currentAlignmentSaveFile)
                self.currentAlignmentSaved = True
            else:
                self.saveAlignmentAsActionImpl()
    
    def saveAlignmentAsActionImpl(self):
        """Implementation of action: File -> Save Alignment as"""
        
        if not self.seqData:
            return
        
        if self.currentAlignmentSaveFile:
            direct = self.currentAlignmentSaveFile
        else:
            direct = os.getcwd()

        acceptedFileTypes = ['fasta', 'txt']
        acceptedFileNames = ['FASTA', 'Text']
        acceptedFilePairs = zip(acceptedFileNames, acceptedFileTypes)
        
        filter = ';;'.join([filePair[0]+'(*.'+filePair[1]+')' 
                                for filePair in acceptedFilePairs])
        saveFileName = QtGui.QFileDialog.getSaveFileName(self, 
                                'Save as file', direct, filter)
        
        extension = saveFileName.split('.')[-1]
        if extension in acceptedFileTypes:
            saveAlignmentToFile(self.seqData, saveFileName)
            self.currentAlignmentSaved = True
            self.currentAlignmentSaveFile = saveFileName
            
        elif extension:
            messageBox = QtGui.QErrorMessage()
            messageBox.setWindowTitle('Error')
            messageBox.showMessage('Alignment sequences format not supported.')
            messageBox.exec_()
    
    def saveSettingsActionImpl(self):
        """Implementation of action: File -> Save Settings"""
        
        if self.currentSettingsSaveFile:
            if not self.allSettingsRecent:
                self.allSettingsRecent = {}
            
            self.allSettingsRecent['aa properties'] = self.aaProperties
            self.allSettingsRecent['property type definition'] = self.aaSettings
            self.allSettingsRecent['property color mapping'] = self.propertyColors
            
            saveSettingsToFile(self.allSettingsRecent, self.currentSettingsSaveFile)
            self.currentSettingsSaved = True
            
        else:
            self.saveSettingsAsActionImpl()
    
    def saveSettingsAsActionImpl(self):
        """Implementation of action: File -> Save Settings as"""
        
        if self.currentSettingsSaveFile:
            direct = self.currentSettingsSaveFile
        else:
            direct = os.getcwd()

        acceptedFileTypes = ['xml', 'txt']
        acceptedFileNames = ['XML', 'Text']
        acceptedFilePairs = zip(acceptedFileNames, acceptedFileTypes)
        
        filter = ';;'.join([filePair[0]+'(*.'+filePair[1]+')' 
                                for filePair in acceptedFilePairs])
        saveFileName = QtGui.QFileDialog.getSaveFileName(self, 
                                'Save as file', direct, filter)
        
        extension = saveFileName.split('.')[-1]
        if extension in acceptedFileTypes:
            if not self.allSettingsRecent:
                self.allSettingsRecent = {}
            
            self.allSettingsRecent['aa properties'] = self.aaProperties
            self.allSettingsRecent['property type definition'] = self.aaSettings
            self.allSettingsRecent['property color mapping'] = self.propertyColors
            
            saveSettingsToFile(self.allSettingsRecent, saveFileName)
            self.currentSettingsSaved = True
            self.currentSettingsSaveFile = saveFileName
            
        elif extension:
            messageBox = QtGui.QErrorMessage()
            messageBox.setWindowTitle('Error')
            messageBox.showMessage('Settings file format not supported.')
            messageBox.exec_()
    
    def printViewActionImpl(self):
        """Implementation of action: File -> Print View"""

        def printViewProcessing(printer):
            painter = QtGui.QPainter()
            painter.begin(printer)
            painter.setRenderHint(QtGui.QPainter.Antialiasing)
            self.scene.render(painter)
            painter.end()
        
        printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution)
        printer.setPaperSize(QtGui.QPrinter.A4)
        printer.setOrientation(QtGui.QPrinter.Landscape)
        printer.setOutputFormat(QtGui.QPrinter.NativeFormat)
        
        printDialog = QtGui.QPrintDialog(printer)
        printDialog.accepted.connect(printViewProcessing)
        printDialog.exec_()

    
    def printSettingsActionImpl(self):
        """Implementation of action: File -> Print Settings"""
        
        self.allSettingsRecent['aa properties'] = \
                            copy.deepcopy(self.aaProperties)
        self.allSettingsRecent['property type definition'] = \
                            copy.deepcopy(self.aaSettings)
        self.allSettingsRecent['property color mapping'] = \
                            copy.deepcopy(self.propertyColors)
        
        saveSettingsRecent(self.allSettingsRecent)
        
        with open(FILE_SETTINGS_RECENT) as f:
            settingsText = f.read()
        settingsDocument = QtGui.QTextDocument(settingsText)
        
        printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution)
        printer.setPaperSize(QtGui.QPrinter.A4)
        printer.setOrientation(QtGui.QPrinter.Landscape)
        printer.setOutputFormat(QtGui.QPrinter.NativeFormat)
        
        printDialog = QtGui.QPrintDialog(printer)
        if printDialog.exec_() == QtGui.QDialog.Accepted:
            settingsDocument.print_(printDialog.printer())
    
    def exitActionImpl(self):
        """Implementation of action: File -> Exit"""
        self.close()
    
    def closeEvent(self, event):
        """
        Reimplementation of closeEvent when close() is called
        Pop up for the confirmation if image, alignment or settings not saved
        """
        def saveFilesQuestioning():
            """Setup the interactive message box"""
            string = ''
            if not self.currentViewSaved:
                string += 'Current alignment image'
                
                if not self.currentAlignmentSaved:
                    string += ', alignment sequences'
                    
                if not self.currentSettingsSaved:
                    string += ', settings'
                
            elif not self.currentAlignmentSaved:
                string += 'Current alignment sequences'
                
                if not self.currentSettingsSaved:
                    string += ', settings'
                    
            elif not self.currentSettingsSaved:
                string += 'Current settings'
            
            if string:
                string += ' not saved.'
                string += ' Cancel exiting to save or continue to exit?'
                
                messageBox = QtGui.QMessageBox()
                messageBox.setWindowTitle('Exiting ...')
                messageBox.setText(string)
                
                messageBox.addButton(QtGui.QMessageBox.Cancel)
                messageBox.addButton(QtGui.QMessageBox.Close)
                
                if messageBox.exec_() == QtGui.QMessageBox.Cancel:
                    event.ignore()
        
        if self.scene.items():
            saveFilesQuestioning()
        
        # construct the recent settings variable if there is not any
        if not self.allSettingsRecent:
            self.allSettingsRecent = {}
        
        self.allSettingsRecent['aa properties'] = self.aaProperties
        self.allSettingsRecent['property type definition'] = self.aaSettings
        self.allSettingsRecent['property color mapping'] = self.propertyColors

        saveSettingsRecent(self.allSettingsRecent)
    
    def selectAllActionImpl(self):
        """
        Select all alignment units in current view
        Implementation of action: Edit -> Select All
        """
        for alignLine in self.view.alignUnitItems:
            for unit in alignLine:
                unit.setSelected(True)
        
        self.deleteTruncateAction.setEnabled(True)
        self.moveRightAction.setEnabled(True)
    
    def undoActionImpl(self):
        """
        Undo the most recent move action
        Implementation of action: Edit -> Undo
        """
        self.undoStack.undo()
        self.refreshViewAction.trigger()
        if self.undoStack.index() == 0:
            self.undoAction.setEnabled(False)
    
    def redoActionImpl(self):
        """
        Redo the most recent move action
        Implementation of action: Edit -> Redo
        """
        self.undoStack.redo()
        self.refreshViewAction.trigger()
        self.undoAction.setEnabled(True)
    
    def addAlignLineActionImpl(self):
        """
        Add an alignment line to current view
        The line will be placed at the leftmost of the frame
        Implementation of action: Edit -> Add Alignment Line
        """
        self.view.drawAlignmentLine(self)
    
    def removeAlignLineActionImpl(self):
        """
        Remove selected alignment lines from current view
        Implementation of action: Edit -> Remove Alignment Line
        """
        selectedItems = self.scene.selectedItems()
        for selectedItem in selectedItems:
            if selectedItem not in self.view.alignmentLines:
                messageBox = QtGui.QMessage()
                messageBox.setWindowTitle('Error')
                messageBox.setText('You selected some items other than alignment lines. '
                                    'This action will be ignored.')
                messageBox.addButton(QtGui.QMessageBox.Ok)
                messageBox.exec_()
                return
        
        for selectedItem in selectedItems:
            self.scene.removeItem(selectedItem)
            self.view.alignmentLines.remove(selectedItem)
        
        self.removeAlignLineAction.setEnabled(False)
    
    def addRegionShadeActionImpl(self):
        """
        Shade the selected region for current view
        Implementation of action: Edit -> Add Region Shade
        """
        def buttonGroupRegionColorAction(btn):
            if btn.bindingColor:
                self.selectedRegionColor = btn.bindingColor
            else:
                self.selectedRegionColor = QtGui.QColorDialog().getColor()
        
            if self.selectedRegionColor.isValid():
                self.dialog.close()
        
        messageBox = QtGui.QMessageBox()
        messageBox.setWindowTitle('Confirmation')
        messageBox.setText('To shade the selected region, click Apply and choose a color. '
                        'Otherwise, click Cancel to cancel this action.')
        messageBox.addButton(QtGui.QMessageBox.Apply)
        messageBox.addButton(QtGui.QMessageBox.Cancel)
        
        if messageBox.exec_() == QtGui.QMessageBox.Cancel:
            return
        
        selectedItems = self.scene.selectedItems()
        selectedCoordinates = getSelectedLinesCoordinates(selectedItems)
        row = list(selectedCoordinates.keys())[0]
        region = tuple(selectedCoordinates[row])
        
        self.selectedRegionColor = None
        if not self.view.shadowedRegions and not self.shadeColorsRecent:
            self.selectedRegionColor = QtGui.QColorDialog().getColor()
        else:
            dialog = QtGui.QDialog()
            dialog.setWindowTitle('Select a color')
            dialog.setMaximumSize(600, 450)
            dialog.setStyleSheet('QWidget { background-color: white}')
            vbox = QtGui.QVBoxLayout()
            buttonGroup = QtGui.QButtonGroup()
            
            if self.view.shadowedRegions:
                widget = QtGui.QWidget()
                scrollArea = QtGui.QScrollArea()
                scrollArea.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
                
                colorList = []
                regionList = []
                for shadowedRegion in sorted(list(self.view.shadowedRegions.keys())):
                    regionColor = self.view.shadowedRegions[shadowedRegion][0]
                    if regionColor not in colorList:
                        colorList.append(regionColor)
                        regionList.append([shadowedRegion])
                    else:
                        colorIndex = colorList.index(regionColor)
                        regionList[colorIndex].append(shadowedRegion)
                
                vboxScrollArea = QtGui.QVBoxLayout()
                for i in range(len(colorList)):
                    hbox = QtGui.QHBoxLayout()
                    regionColor = colorList[i]
                    
                    radioButton = QtGui.QRadioButton()
                    radioButton.bindingColor = regionColor
                    hbox.addWidget(radioButton)
                    buttonGroup.addButton(radioButton)
                    
                    regionText = str((regionList[i][0][0]+1, regionList[i][0][1]+1))
                    label = QtGui.QLabel(regionText)
                    label.setStyleSheet('QWidget { background-color: rgba(%d, %d, %d, %d%%)}' % 
                                        (regionColor.red(), regionColor.green(), regionColor.blue(), 60))
                    hbox.addWidget(label, 0, QtCore.Qt.AlignLeft)
                    
                    for j in range(1, len(regionList[i])):
                        hbox.addWidget(QtGui.QLabel(', '))
                        regionText = str((regionList[i][j][0]+1, regionList[i][j][1]+1))
                        label = QtGui.QLabel(regionText)
                        label.setStyleSheet('QWidget { background-color: rgba(%d, %d, %d, %d%%)}' % 
                                            (regionColor.red(), regionColor.green(), regionColor.blue(), 60))
                        hbox.addWidget(label, 0, QtCore.Qt.AlignLeft)
                    
                    hbox.addStretch(1)
                    vboxScrollArea.addLayout(hbox)
                
                widget.setLayout(vboxScrollArea)
                scrollArea.setWidget(widget)
                
                vbox.addWidget(QtGui.QLabel('Select one color from those currently in use:'))
                vbox.addWidget(scrollArea)
                vbox.addWidget(QtGui.QLabel('Or'))

            if self.shadeColorsRecent:
                widget = QtGui.QWidget()
                scrollArea = QtGui.QScrollArea()
                scrollArea.setAttribute(QtCore.Qt.WA_TranslucentBackground, True)
                
                grid = QtGui.QGridLayout()
                nColorPerRow = 5
                iColor = 0
                iRow = 0
                iCol = 0
                
                for color in self.shadeColorsRecent[::-1]:
                    radioButton = QtGui.QRadioButton()
                    radioButton.bindingColor = color
                    grid.addWidget(radioButton, iRow, iCol, 1, 1)
                    buttonGroup.addButton(radioButton)
                    
                    label = QtGui.QLabel('        ')
                    label.setStyleSheet('QWidget { background-color: rgba(%d, %d, %d, %d%%)}' % 
                                        (color.red(), color.green(), color.blue(), 60))
                    grid.addWidget(label, iRow, iCol+1, 1, 2)
                    
                    iColor += 1
                    if iColor % nColorPerRow == 0:
                        iRow += 1
                        iCol = 0
                    else:
                        label = QtGui.QLabel('   ')
                        grid.addWidget(label, iRow, iCol+3, 1, 1)
                        iCol += 4

                widget.setLayout(grid)
                scrollArea.setWidget(widget)
                            
                vbox.addWidget(QtGui.QLabel('Select one color from those recently used by ccSeek:'))
                vbox.addWidget(scrollArea)
                vbox.addWidget(QtGui.QLabel('Or'))

            radioButton = QtGui.QRadioButton('Select one from color board')
            radioButton.bindingColor = None
            vbox.addWidget(radioButton)
            buttonGroup.addButton(radioButton)
            
            buttonGroup.buttonClicked.connect(buttonGroupRegionColorAction)
            dialog.setLayout(vbox)
            self.dialog = dialog
            self.dialog.exec_()
        
        color = self.selectedRegionColor
        if color and color.isValid():
            self.view.drawRegionShadow(self, region, color)
            
            if color in self.shadeColorsRecent:
                self.shadeColorsRecent.remove(color)
            
            if len(self.shadeColorsRecent) < 40:   # 40 is the limit to store recent colors
                self.shadeColorsRecent.append(color)
            else:
                self.shadeColorsRecent.pop(0)
                self.shadeColorsRecent.append(color)
            
            putShadeColorsRecent(self.shadeColorsRecent)
        
        determineActionsToEnable(selectedItems[0])
    
    def removeRegionShadeActionImpl(self):
        """
        Remove the shade for currently selected region
        Implementation of action: Edit -> Remove Region Shade
        """
        selectedItems = self.scene.selectedItems()
        selectedCoordinates = getSelectedLinesCoordinates(selectedItems)
        row = list(selectedCoordinates.keys())[0]
        region = tuple(selectedCoordinates[row])
        
        self.scene.removeItem(self.view.shadowedRegions[region][1])
        self.view.shadowedRegions.pop(region, None)
        determineActionsToEnable(selectedItems[0])

    def removeEditedGapsActionImpl(self):
        """
        Remove all gaps that are introduced in this series of edit operations
        Implementation of action: Edit -> Remove Edited Gaps
        """
        messageBox = QtGui.QMessageBox()
        messageBox.setWindowTitle('Confirmation')
        messageBox.setText('Are you sure to remove all newly added gaps? '
                'This action is not undoable.')
        messageBox.addButton(QtGui.QMessageBox.Ok)
        messageBox.addButton(QtGui.QMessageBox.Cancel)
        
        if messageBox.exec_() == QtGui.QMessageBox.Cancel:
            return

        self.seqData = copy.deepcopy(self.seqDataNoNewGaps)
        self.scene.clearSelection()
        self.refreshViewAction.trigger()
        self.undoStack.clear()
        self.undoAction.setEnabled(False)
        self.redoAction.setEnabled(False)
        determineActionsToEnable(self.view.alignUnitItems[0][0])
    
    def deleteTruncateActionImpl(self):
        """
        Delete or truncate the selected sequences lines from view
        Implementation of action: Edit -> Delete/Truncate
        """
        deleteTruncateCmd = DeleteTruncateCommand(self)
        self.undoAction.setEnabled(True)
        self.redoAction.setEnabled(True)
        self.undoStack.push(deleteTruncateCmd)
    
    def restoreInitialAlignActionImpl(self):
        """
        Restore the alignment to initial state as read from input
        Implementation of action: Edit -> Restore Initial Alignment
        """
        messageBox = QtGui.QMessageBox()
        messageBox.setWindowTitle('Confirmation')
        messageBox.setText('Are you sure to restore the alignments to the '
                'input file state? All newly added gaps will be removed, '
                'the deleted sequences will come back and '
                'the sequence order will be restored (up/down). '
                'This action is not undoable.')
        messageBox.addButton(QtGui.QMessageBox.Ok)
        messageBox.addButton(QtGui.QMessageBox.Cancel)
        
        if messageBox.exec_() == QtGui.QMessageBox.Cancel:
            return

        self.seqData = copy.deepcopy(self.seqDataDefault)
        self.scene.clearSelection()
        self.refreshViewAction.trigger()
        self.undoStack.clear()
        self.undoAction.setEnabled(False)
        self.redoAction.setEnabled(False)
        self.selectAllAction.setEnabled(True)
        determineActionsToEnable(self.view.alignUnitItems[0][0])

    def moveRightActionImpl(self):
        """Implementation of action Edit -> Move Right"""
        actionMoveCmd = ActionMoveCommand('RIGHT', self)
        self.undoAction.setEnabled(True)
        self.redoAction.setEnabled(True)
        self.undoStack.push(actionMoveCmd)
    
    def moveLeftActionImpl(self):
        """Implementation of action: Edit -> Move Left"""
        actionMoveCmd = ActionMoveCommand('LEFT', self)
        self.undoAction.setEnabled(True)
        self.redoAction.setEnabled(True)
        self.undoStack.push(actionMoveCmd)
    
    def moveUpActionImpl(self):
        """
        Implementation of action: Edit -> Move Up
        Applicable only when entire line(s) is /are selected
        """
        actionMoveCmd = ActionMoveCommand('UP', self)
        self.undoAction.setEnabled(True)
        self.redoAction.setEnabled(True)
        self.undoStack.push(actionMoveCmd)
    
    def moveDownActionImpl(self):
        """
        Implementation of action: Edit -> Move Down
        Applicable only when entire line(s) is /are selected
        """
        actionMoveCmd = ActionMoveCommand('DOWN', self)
        self.undoAction.setEnabled(True)
        self.redoAction.setEnabled(True)
        self.undoStack.push(actionMoveCmd)

    def resetSettingActionImpl(self):
        """Implementation of action: Settings -> Reset to default"""
        messageBox = QtGui.QMessageBox()
        messageBox.setWindowTitle('Confirmation')
        messageBox.setText('Are you sure to reset to default settings? '
                    'If you are viewing a non-default property, '
                    'the program will set "identity" as current property.')
        messageBox.addButton(QtGui.QMessageBox.Ok)
        messageBox.addButton(QtGui.QMessageBox.Cancel)
        
        if messageBox.exec_() == QtGui.QMessageBox.Cancel:
            return
        
        self.allSettingsDefault = getSettingsDefault()
        
        self.aaProperties = copy.deepcopy(
                        self.allSettingsDefault['aa properties'])
        self.aaSettings = copy.deepcopy(
                        self.allSettingsDefault['property type definition'])
        self.propertyColors = copy.deepcopy(
                        self.allSettingsDefault['property color mapping'])
        
        # need re-determine the property types for each amino acid
        self.aaPropertyTypes = self.determinePropertyType()
        
        # set the alignment property to default "identity" for safety
        if self.propertyCurrent not in self.aaSettings.keys():
            self.propertyCurrent = 'identity'
            self.identityViewAction.setChecked(True)

        # re-enable to the view option of more properties
        allProperties = set(self.aaSettings.keys())
        customizedProperties = allProperties - set(self.defaultProperties)
        if not customizedProperties:
            self.moreViewAction.setEnabled(False)
        else:
            self.moreViewAction.setEnabled(True)
        
        # refresh the image using current settings and property
        self.refreshViewActionImpl()
    
    def saveDefaultSettingActionImpl(self):
        """Implementation of action: Settings -> Save as Default"""
        messageBox = QtGui.QMessageBox()
        messageBox.setWindowTitle('Confirmation')
        messageBox.setText('Are you sure to overwrite the default settings?')
        messageBox.addButton(QtGui.QMessageBox.Ok)
        messageBox.addButton(QtGui.QMessageBox.Cancel)
        
        if messageBox.exec_() == QtGui.QMessageBox.Cancel:
            return
        
        self.allSettingsDefault['aa properties'] = \
                            copy.deepcopy(self.aaProperties)
        self.allSettingsDefault['property type definition'] = \
                            copy.deepcopy(self.aaSettings)
        self.allSettingsDefault['property color mapping'] = \
                            copy.deepcopy(self.propertyColors)
        
        saveSettingsDefault(self.allSettingsDefault)
    
    def sizeSettingActionImpl(self):
        """Implementation of action: Settings -> Size"""
        self.configPropertySetting('size')
    
    def chargeSettingActionImpl(self):
        """Implementation of action: Settings -> Charge"""
        self.configPropertySetting('charge')
    
    def polaritySettingActionImpl(self):
        """Implementation of action: Settings -> Polarity"""
        self.configPropertySetting('polarity')
    
    def hydroSettingActionImpl(self):
        """Implementation of action: Settings -> Hydropathy"""
        self.configPropertySetting('hydropathy')
    
    def moreSettingActionImpl(self):
        """Implementation of action: Settings -> More properties ..."""
        def editMorePropertyAction(btn):            
            self.configPropertySetting(btn.bindingProperty)
        
        dialog = QtGui.QDialog()
        dialog.setWindowTitle('More properties')
        dialog.setMinimumWidth(420)
        dialog.setMinimumHeight(300)
        
        grid = QtGui.QGridLayout()
        widget = QtGui.QWidget()
        
        introductionLabel = QtGui.QLabel(
                            'Currently available customized properties:')
        grid.addWidget(introductionLabel, 0, 0, 1, 4)
        
        dialog.startRow = 1
        dialog.startColumn = 2
        
        row = dialog.startRow
        col = dialog.startColumn
        
        allProperties = set(self.aaSettings.keys())
        defaultProperties = set(self.defaultProperties)
        customizedProperties = allProperties - defaultProperties        
        
        if not customizedProperties:
            
            naLabel = QtGui.QLabel('Not applicable')
            grid.addWidget(naLabel, row, col, 1, 3)
            
        else:
            
            customizedProperties = sorted(list(customizedProperties))
            buttonGroup = QtGui.QButtonGroup()
            
            for property in customizedProperties:
                
                propertyCheckBox = QtGui.QCheckBox(property)
                grid.addWidget(propertyCheckBox, row, col, 1, 3)
                
                editButton = QtGui.QPushButton('Edit')
                editButton.bindingProperty = property
                
                buttonGroup.addButton(editButton)
                grid.addWidget(editButton, row, col+4, 1, 2)
                
                row += 1
            
            buttonGroup.buttonClicked.connect(editMorePropertyAction)
        
        row += 1
        deleteSelectedButton = QtGui.QPushButton('Delete selected properties')
        addNewButton = QtGui.QPushButton('Add new property')
        cancelButton = QtGui.QPushButton('Cancel')
        
        deleteSelectedButton.clicked.connect(self.deleteSelectedPropertyAction)
        addNewButton.clicked.connect(self.addNewPropertyAction)
        cancelButton.clicked.connect(self.cancelPropertyAction)
        
        grid.addWidget(cancelButton, row, 7, 1, 2)
        grid.addWidget(deleteSelectedButton, row, 0, 1, 3)
        grid.addWidget(addNewButton, row, 4, 1, 3)
        
        for i in range(grid.rowCount()):
            grid.setRowMinimumHeight(i, 30)
        
        widget.setLayout(grid)
        
        scrollArea = QtGui.QScrollArea()
        scrollArea.setWidget(widget)
        
        hbox = QtGui.QHBoxLayout()
        hbox.addWidget(scrollArea)
        dialog.setLayout(hbox)
        
        dialog.grid = grid
        self.morePropertyDialog = dialog
        self.morePropertyDialog.exec_()
    
    def deleteSelectedPropertyAction(self):
        """
        This method implements the behavior when the button 
        "Delete selected properties" is clicked
        """
        allProperties = set(self.aaSettings.keys())
        customizedProperties = allProperties - set(self.defaultProperties)
        
        if not customizedProperties:
            messageBox = QtGui.QMessageBox()
            messageBox.setWindowTitle('Error')
            messageBox.setText('There is no customized property to delete.')
            messageBox.exec_()
            
        else:
            
            messageBox = QtGui.QMessageBox()
            messageBox.setWindowTitle('Confirmation')
            messageBox.setText('Are you sure to delete '
                                        'all selected properties?')
            messageBox.addButton(QtGui.QMessageBox.Ok)
            messageBox.addButton(QtGui.QMessageBox.Cancel)
            
            if messageBox.exec_() == QtGui.QMessageBox.Cancel:
                return
            
            dialog = self.morePropertyDialog
            grid = dialog.grid
            row = dialog.startRow
            col = dialog.startColumn
            rowKept = row
            
            widget = grid.itemAtPosition(row, col).widget()
            while isinstance(widget, QtGui.QCheckBox):
                
                if widget.checkState() == QtCore.Qt.Checked:
                    
                    grid.removeWidget(widget)
                    widget.setVisible(False)
                    
                    editButton = grid.itemAtPosition(row, col+4).widget()
                    grid.removeWidget(editButton)
                    editButton.setVisible(False)
                    
                    self.removeProperty(widget.text())
                    
                else:
                    
                    grid.addWidget(widget, rowKept, col, 1, 3)
                    rowKept += 1
                
                row += 1
                if grid.itemAtPosition(row, col):
                    widget = grid.itemAtPosition(row, col).widget()
                else:
                    widget = None
            
            allProperties = set(self.aaSettings.keys())
            customizedProperties = allProperties - set(self.defaultProperties)
            if not customizedProperties:
                self.moreViewAction.setEnabled(False)
            
            self.morePropertyDialog.close()
    
    def addNewPropertyAction(self):
        """
        This method implements the behavior when the button 
        "Add new property" is clicked
        """
        dialog = QtGui.QDialog()
        dialog.setMinimumWidth(200)
        dialog.setMinimumHeight(150)
        dialog.setWindowTitle('Add new property')
        
        grid = QtGui.QGridLayout()
        
        introductionLabel = QtGui.QLabel('Enter the new property name:')
        grid.addWidget(introductionLabel, 0, 0)
        
        newPropertyNameEdit = QtGui.QLineEdit()
        grid.addWidget(newPropertyNameEdit, 1, 1)
        
        continueButton = QtGui.QPushButton('Continue')
        cancelButton = QtGui.QPushButton('Cancel')
        
        continueButton.clicked.connect(self.continueNewPropertyAction)
        cancelButton.clicked.connect(self.cancelNewPropertyAction)
        
        grid.addWidget(cancelButton, 2, 5)
        grid.addWidget(continueButton, 2, 3)
        
        dialog.setLayout(grid)
        
        self.addNewPropertyDialog = dialog
        self.addNewPropertyDialog.exec_()
    
    def continueNewPropertyAction(self):
        """Cotinue to add a new property"""
        dialog = self.addNewPropertyDialog
        property = dialog.layout().itemAtPosition(1, 1).widget().text().lower()
        
        messageBox = QtGui.QMessageBox()
        messageBox.setWindowTitle('Error')
        
        if not property:
            messageBox.setText('Empty property name is not allowed.')
            messageBox.exec_()
            
        elif not property.isalnum():
            messageBox.setText('Property name must be alphanumeric.')
            messageBox.exec_()
            
        elif property in self.aaSettings.keys():
            messageBox.setText('The property name already exists. '
                                'Please try a unique one.')
            messageBox.exec_()
            
        else:
            self.propertyEditFinished = False
            self.configPropertySetting(property)
            
            if self.propertyEditFinished:
                self.moreViewAction.setEnabled(True)
                self.addNewPropertyDialog.close()
                self.morePropertyDialog.close()
    
    def cancelNewPropertyAction(self):
        """Cancel without adding a new property"""
        self.addNewPropertyDialog.close()
    
    def cancelPropertyAction(self):
        """Cancel without editing customized amino acid properties"""
        self.morePropertyDialog.close()
    
    def removeProperty(self, property):
        """
        This method implements the actual operations 
        when a user deletes a property
        """
        if property == self.propertyCurrent:
            messageBox = QtGui.QMessageBox()
            messageBox.setWindowTitle('Error')
            messageBox.setText('The property is currently in view. '
                    'You cannot delete this property unless you change to '
                    'view a different property')
            messageBox.exec_()            
        else:            
            AAs = self.aaProperties.keys()
            for aa in AAs:
                self.aaProperties[aa].pop(property)
            
            self.aaSettings.pop(property)
            self.propertyColors.pop(property)
    
    def configPropertySetting(self, property):
        """
        This method implements interactions by a user on a property settings
        Users may chenge the property value for any amino acids
        Users may also re-define the categories for the property
        """
        dialog = QtGui.QDialog()
        dialog.setWindowTitle(property.title())
        dialog.setFixedWidth(560)
        dialog.setFixedHeight(480)
        
        grid = QtGui.QGridLayout()
        dialog.gridRow = 24
        dialog.gridColumn = 18
        dialog.gridMinWidth = 10
        dialog.gridMinHeight = 10
        
        for i in range(dialog.gridColumn):
            grid.setColumnMinimumWidth(i, dialog.gridMinWidth)
        
        for i in range(dialog.gridRow):
            grid.setRowMinimumHeight(i, dialog.gridMinHeight)
        
        # instance variables for communication with buttons
        dialog.startRow = 0
        dialog.startColumn = 0
        dialog.intraSectionRowSpacing = 0
        dialog.interSectionRowSpacing = 2
        dialog.aaLabelWidth = 1
        dialog.aaValueEditWidth = 2
        dialog.aaSpacing = 2
        dialog.aaPerRow = 4
        dialog.categoryCheckBoxWidth = 5
        dialog.categoryIntervalLowerStart = 6
        dialog.categoryIntervalBoundWidth = 2
        dialog.categoryValueTextLabelWidth = 4
        dialog.numOfCategories = 0
        
        saveAsDefaultButton = QtGui.QPushButton('Save as default')
        updateButton = QtGui.QPushButton('Update')
        cancelButton = QtGui.QPushButton('Cancel')
        
        saveAsDefaultButton.clicked.connect(self.saveAsDefaultSettingConfig)
        updateButton.clicked.connect(self.updateSettingConfig)
        cancelButton.clicked.connect(self.cancelSettingConfig)
        
        grid.addWidget(cancelButton, dialog.gridRow-2, 
                       dialog.gridColumn-4, 2, 4)
        grid.addWidget(saveAsDefaultButton, dialog.gridRow-2, 0, 2, 6)
        grid.addWidget(updateButton, dialog.gridRow-2, 
                       dialog.gridColumn-8, 2, 4)
        
        i = 0
        row = dialog.startRow
        col = dialog.startColumn
        propertyExists = True if property in self.aaSettings.keys() else False
        
        for aa in AA_LIST:
            
            aaLabel = QtGui.QLabel(aa)
            grid.addWidget(aaLabel, row, col, 1, dialog.aaLabelWidth)
            col += dialog.aaLabelWidth
            
            if propertyExists:
                aaValueEdit = QtGui.QLineEdit(str(
                                                  self.aaProperties[aa][property]))
            else:
                aaValueEdit = QtGui.QLineEdit('0')
            
            grid.addWidget(aaValueEdit, row, col, 1, dialog.aaValueEditWidth)
            col += dialog.aaValueEditWidth + dialog.aaSpacing
            
            i += 1
            if i % dialog.aaPerRow == 0:
                col = dialog.startColumn
                row += 1 + dialog.intraSectionRowSpacing
        
        row += dialog.interSectionRowSpacing
        col = dialog.startColumn
        
        if property in self.aaSettings.keys():            
            # display the categories based on the order of value intervals
            categoryIntervals = sorted(list(self.aaSettings[property].values()))
            categories = self.aaSettings[property].keys()
            
            # determine the category for each sorted interval
            for interval in categoryIntervals:                
                for category in categories:                    
                    if self.aaSettings[property][category] == interval:                        
                        break
                
                categoryCheckBox = QtGui.QCheckBox(category)
                grid.addWidget(categoryCheckBox, row, col, 
                               1, dialog.categoryCheckBoxWidth)
                col = dialog.categoryIntervalLowerStart
                
                intervalLowerEdit = QtGui.QLineEdit(str(
                                self.aaSettings[property][category][0]))
                grid.addWidget(intervalLowerEdit, row, col, 
                               1, dialog.categoryIntervalBoundWidth)
                col += dialog.categoryIntervalBoundWidth
                
                valueTextLabel = QtGui.QLabel(' <=      value      < ')
                grid.addWidget(valueTextLabel, row, col, 
                               1, dialog.categoryValueTextLabelWidth)
                col += dialog.categoryValueTextLabelWidth
                
                intervalUpperEdit = QtGui.QLineEdit(str(
                                self.aaSettings[property][category][1]))
                grid.addWidget(intervalUpperEdit, row, col, 
                               1, dialog.categoryIntervalBoundWidth)
                
                row += 1 + dialog.intraSectionRowSpacing
                col = dialog.startColumn
                
                dialog.numOfCategories += 1
        
        deleteSelectedButton = QtGui.QPushButton('Delete selected category')
        addNewButton = QtGui.QPushButton('Add new category')
        
        deleteSelectedButton.clicked.connect(self.deleteSelectedCategoryAction)
        addNewButton.clicked.connect(self.addNewCategoryAction)
        
        grid.addWidget(deleteSelectedButton, row, 0, 2, 7)
        grid.addWidget(addNewButton, row, dialog.gridColumn-5, 2, 5)
        
        cancelButton.setDefault(True)
        dialog.setLayout(grid)
        dialog.grid = grid
        
        self.settingConfigDialog = dialog
        
        if not propertyExists:
            self.addNewCategoryAction()
        
        self.settingConfigDialog.exec_()
    
    def saveAsDefaultSettingConfig(self):
        """
        This method saves current property settings as default
        If there are any new properties for which the colors are not setup yet,
        the program will automatically assign them all 'black' for data consistency
        """
        messageBox = QtGui.QMessageBox()
        messageBox.setWindowTitle('Confirmation')
        messageBox.setText('Are you sure to overwrite the default settings?')
        messageBox.addButton(QtGui.QMessageBox.Ok)
        messageBox.addButton(QtGui.QMessageBox.Cancel)
        
        if messageBox.exec_() == QtGui.QMessageBox.Cancel:
            return
        
        result = self.extractSettingDialogResult()
        if not result:          # invalid result
            return
        
        dialogSetting = copy.deepcopy(result)
        self.allSettingsDefault = getSettingsDefault()
        
        property = dialogSetting['property']
        for aa in self.allSettingsDefault['aa properties'].keys():
            self.allSettingsDefault['aa properties'][aa][property] = \
                        dialogSetting['aa'][aa]
        
        self.allSettingsDefault['property type definition'][property] = \
                        dialogSetting['category']
        
        if property in self.allSettingsDefault['property color mapping'].keys():
            originalCategoryColors = copy.deepcopy(
                    self.allSettingsDefault['property color mapping'][property])
        else:
            originalCategoryColors = {}
        
        self.allSettingsDefault['property color mapping'][property] = {}
        
        for category in dialogSetting['category'].keys():            
            if category in originalCategoryColors.keys():
                self.allSettingsDefault['property color mapping']\
                        [property][category] = originalCategoryColors[category]
            else:
                self.allSettingsDefault['property color mapping']\
                        [property][category] = QtGui.QColor('#000000')
        
        self.allSettingsDefault['property color mapping']\
                        [property]['undefined'] = QtGui.QColor(QtCore.Qt.white)
        
        saveSettingsDefault(self.allSettingsDefault)
    
    def updateSettingConfig(self):
        """
        This method just update current settings in program 
        It won't change default settings
        """
        messageBox = QtGui.QMessageBox()
        messageBox.setWindowTitle('Confirmation')
        messageBox.setText('Are you sure to update current settings?')
        messageBox.addButton(QtGui.QMessageBox.Ok)
        messageBox.addButton(QtGui.QMessageBox.Cancel)
        
        if messageBox.exec_() == QtGui.QMessageBox.Cancel:
            return
        
        result = self.extractSettingDialogResult()
        if not result:          # invalid result
            return
        
        dialogSetting = copy.deepcopy(result)
        property = dialogSetting['property']
        
        for aa in dialogSetting['aa'].keys():
            self.aaProperties[aa][property] = dialogSetting['aa'][aa]
        
        self.aaSettings[property] = dialogSetting['category']
        
        if property in self.propertyColors.keys():
            originalCategoryColors = \
                    copy.deepcopy(self.propertyColors[property])
        else:
            originalCategoryColors = {}
        
        self.propertyColors[property] = {}
        
        for category in dialogSetting['category'].keys():
            
            if category in originalCategoryColors.keys():
                self.propertyColors[property][category] = \
                            originalCategoryColors[category]
            else:
                self.propertyColors[property][category] = \
                            QtGui.QColor(QtCore.Qt.black)
        
        self.propertyColors[property]['undefined'] = \
                            QtGui.QColor(QtCore.Qt.white)
        
        self.aaPropertyTypes = self.determinePropertyType()
        if property == self.propertyCurrent:
            self.refreshViewActionImpl()
        
        self.propertyEditFinished = True
        self.currentSettingsSaved = False
        self.settingConfigDialog.close()
    
    def cancelSettingConfig(self):
        """Cancel the editing for current property opened in dialog"""
        self.propertyEditFinished = False
        self.settingConfigDialog.close()
    
    def deleteSelectedCategoryAction(self):
        """
        This method implements the deletion of selected property categories
        All categories are possible to be deleted with none left
        """
        dialog = self.settingConfigDialog
        grid = self.settingConfigDialog.grid
        
        messageBox = QtGui.QMessageBox()
        if dialog.numOfCategories == 0:
            messageBox.setWindowTitle('Error')
            messageBox.setText('There are no categories '
                                    'defined for current property.')
            messageBox.exec_()
            return

        messageBox.setWindowTitle('Confirmation')
        messageBox.setText('Are you sure to delete '
                                    'all selected property categories?')
        messageBox.addButton(QtGui.QMessageBox.Ok)
        messageBox.addButton(QtGui.QMessageBox.Cancel)        
        if messageBox.exec_() == QtGui.QMessageBox.Cancel:
            return
        
        aaRow = 20 / dialog.aaPerRow * (1 + dialog.intraSectionRowSpacing)
        row = dialog.startRow + aaRow + dialog.interSectionRowSpacing
        col = dialog.startColumn
        
        rowKept = row
        firstRowWidget = grid.itemAtPosition(row, col).widget()
        
        while isinstance(firstRowWidget, QtGui.QCheckBox) or \
                    isinstance(firstRowWidget, QtGui.QLineEdit):
            
            categoryCheckBox = firstRowWidget
            colCategoryCheckBox = col
            col = dialog.categoryIntervalLowerStart
            
            intervalLowerEdit = grid.itemAtPosition(row, col).widget()
            colIntervalLowerEdit = col
            col += dialog.categoryIntervalBoundWidth
            
            valueTextLabel = grid.itemAtPosition(row, col).widget()
            colValueTextLabel = col
            col += dialog.categoryValueTextLabelWidth
            
            intervalUpperEdit = grid.itemAtPosition(row, col).widget()
            colIntervalUpperEdit = col
            
            if isinstance(firstRowWidget, QtGui.QLineEdit):
                newCategoryDoneButton = \
                        grid.itemAtPosition(row, dialog.gridColumn-3).widget()
            
            row += 1 + dialog.intraSectionRowSpacing
            col = dialog.startColumn
            
            if isinstance(categoryCheckBox, QtGui.QCheckBox) and \
                    categoryCheckBox.checkState() == QtCore.Qt.Checked:
                
                grid.removeWidget(categoryCheckBox)
                categoryCheckBox.setVisible(False)
                
                grid.removeWidget(intervalLowerEdit)
                intervalLowerEdit.setVisible(False)
                
                grid.removeWidget(valueTextLabel)
                valueTextLabel.setVisible(False)
                
                grid.removeWidget(intervalUpperEdit)
                intervalUpperEdit.setVisible(False)
                
                dialog.numOfCategories -= 1
                
            else:

                grid.addWidget(categoryCheckBox, rowKept, colCategoryCheckBox, 
                               1, dialog.categoryCheckBoxWidth)
                grid.addWidget(intervalLowerEdit, rowKept, colIntervalLowerEdit, 
                               1, dialog.categoryIntervalBoundWidth)
                grid.addWidget(valueTextLabel, rowKept, colValueTextLabel, 
                               1, dialog.categoryValueTextLabelWidth)
                grid.addWidget(intervalUpperEdit, rowKept, colIntervalUpperEdit, 
                               1, dialog.categoryIntervalBoundWidth)
                
                if isinstance(categoryCheckBox, QtGui.QLineEdit):
                    grid.addWidget(newCategoryDoneButton, rowKept, 
                                   dialog.gridColumn-3, 1, 3)
                
                rowKept += 1 + dialog.intraSectionRowSpacing
            
            firstRowWidget = grid.itemAtPosition(row, col).widget()
        
        deleteSelectedButton = grid.itemAtPosition(row, 0).widget()
        grid.addWidget(deleteSelectedButton, rowKept, 0, 2, 7)
        
        addNewButton = grid.itemAtPosition(row, dialog.gridColumn-5).widget()
        grid.addWidget(addNewButton, rowKept, dialog.gridColumn-5, 2, 5)
    
    def addNewCategoryAction(self):
        """This method is to add a new category for current property opened"""
        dialog = self.settingConfigDialog
        grid = self.settingConfigDialog.grid
        
        if dialog.numOfCategories == 8:
            messageBox = QtGui.QMessageBox()
            messageBox.setWindowTitle('Error')
            messageBox.setText('You already have 8 categories '
                        'for this property. If you do want more, '
                        'try using the property "identity" instead.')
            messageBox.exec_()
            return
        
        aaRow = 20 / dialog.aaPerRow * (1 + dialog.intraSectionRowSpacing)
        categoryRow = dialog.numOfCategories * (1 + dialog.intraSectionRowSpacing)
        row = dialog.startRow + aaRow + dialog.interSectionRowSpacing + categoryRow
        
        deleteSelectedButton = grid.itemAtPosition(row, 0).widget()
        addNewButton = grid.itemAtPosition(row, dialog.gridColumn-5).widget()
        
        if not isinstance(deleteSelectedButton, QtGui.QPushButton):            
            messageBox = QtGui.QMessageBox()
            messageBox.setWindowTitle('Error')
            messageBox.setText('You need add multiple categories one at a time. '
                                'Complete last category first and then continue.')
            messageBox.exec_()
            return
        
        newCategoryNameEdit = QtGui.QLineEdit('default')
        newCategoryNameEdit.setMaxLength(20)
        
        newIntervalLowerEdit = QtGui.QLineEdit('-inf')
        newValueTextLabel = QtGui.QLabel(' <=      value      < ')
        newIntervalUpperEdit = QtGui.QLineEdit('inf')
        
        newCategoryDoneButton = QtGui.QPushButton('Done')
        newCategoryDoneButton.clicked.connect(self.newCategoryDoneAction)
        
        grid.addWidget(newCategoryNameEdit, row, 0, 
                       1, dialog.categoryCheckBoxWidth)
        
        col = dialog.categoryIntervalLowerStart
        grid.addWidget(newIntervalLowerEdit, row, col, 
                       1, dialog.categoryIntervalBoundWidth)
        
        col += dialog.categoryIntervalBoundWidth
        grid.addWidget(newValueTextLabel, row, col, 
                       1, dialog.categoryValueTextLabelWidth)
        
        col += dialog.categoryValueTextLabelWidth
        grid.addWidget(newIntervalUpperEdit, row, col, 
                       1, dialog.categoryIntervalBoundWidth)
        
        grid.addWidget(newCategoryDoneButton, row, dialog.gridColumn-3, 1, 3)
        
        row += 1 + dialog.intraSectionRowSpacing        
        grid.addWidget(deleteSelectedButton, row, 0, 2, 7)
        grid.addWidget(addNewButton, row, dialog.gridColumn-5, 2, 5)
    
    def newCategoryDoneAction(self):
        """This methods is called when adding a new category is complete"""
        dialog = self.settingConfigDialog
        grid = self.settingConfigDialog.grid
        
        aaRow = 20 / dialog.aaPerRow * (1 + dialog.intraSectionRowSpacing)
        categoryRow = dialog.numOfCategories * (1 + dialog.intraSectionRowSpacing)
        row = dialog.startRow + aaRow + dialog.interSectionRowSpacing + categoryRow
        
        newCategoryNameEdit = grid.itemAtPosition(row, 0).widget()
        newCategoryCheckBox = QtGui.QCheckBox(newCategoryNameEdit.text())
        newCategoryDoneButton = \
                        grid.itemAtPosition(row, dialog.gridColumn-3).widget()
        
        grid.removeWidget(newCategoryNameEdit)
        grid.removeWidget(newCategoryDoneButton)
        newCategoryNameEdit.setVisible(False)
        newCategoryDoneButton.setVisible(False)
        
        grid.addWidget(newCategoryCheckBox, row, 0, 
                       1, dialog.categoryCheckBoxWidth)
        dialog.numOfCategories += 1
    
    def extractSettingDialogResult(self):
        """
        This method reads and extracts the settings from the dialog
        The data need be valid to be accepted
        """
        dialog = self.settingConfigDialog
        grid = dialog.grid
        
        dialogSetting = {}        
        dialogSetting['property'] = dialog.windowTitle().lower()
        dialogSetting['aa'] = {}
        dialogSetting['category'] = {}
        
        row = dialog.startRow
        col = dialog.startColumn
        i = 0
        
        messageBox = QtGui.QMessageBox()
        messageBox.setWindowTitle('Error')
        
        while i < len(self.aaProperties.keys()):
            
            aa = grid.itemAtPosition(row, col).widget().text()
            col += dialog.aaLabelWidth
            
            valueText = grid.itemAtPosition(row, col).widget().text()
            
            try:
                propertyValue = float(valueText)                
            except ValueError:
                messageBox.setText('Property values need be a number')
                messageBox.exec_()
                return
            
            dialogSetting['aa'][aa] = propertyValue
            
            col += dialog.aaValueEditWidth + dialog.aaSpacing
            i += 1
            
            if i % dialog.aaPerRow == 0:
                
                row += 1 + dialog.intraSectionRowSpacing
                col = dialog.startColumn
        
        row += dialog.interSectionRowSpacing
        col = dialog.startColumn
        categories = set()
        intervals = []
        
        widget = grid.itemAtPosition(row, col).widget()
        while isinstance(widget, QtGui.QCheckBox):
            
            categoryName = widget.text().lower()
            
            if categoryName in categories:
                messageBox.setText('Each category should have a distinct name.')
                messageBox.exec_()
                return
            
            col = dialog.categoryIntervalLowerStart
            intervalLowerText = grid.itemAtPosition(row, col).widget().text()
            
            col += dialog.categoryIntervalBoundWidth + \
                            dialog.categoryValueTextLabelWidth
            intervalUpperText = grid.itemAtPosition(row, col).widget().text()
            
            try:
                intervalLowerValue = float(intervalLowerText)
                intervalUpperValue = float(intervalUpperText)                
            except ValueError:
                messageBox.setText('Category range interval values must be '
                                    'a number (including "inf" and "-inf")')
                messageBox.exec_()
                return
            
            if not intervalLowerValue < intervalUpperValue:
                messageBox.setText('Invalid interval inputs!')
                messageBox.exec_()
                return
            
            categories.add(categoryName)
            intervals.append((intervalLowerValue, intervalUpperValue))
            dialogSetting['category'][categoryName] = \
                                    (intervalLowerValue, intervalUpperValue)
            
            row += 1 + dialog.intraSectionRowSpacing
            col = dialog.startColumn
            widget = grid.itemAtPosition(row, col).widget()
        
        if isinstance(widget, QtGui.QLineEdit):
            messageBox.setWindowTitle('Confirmation')
            messageBox.setText('You have unconfirmed new category. '
                'Continue to ignore it or cancel to complete the editing?')
            messageBox.addButton(QtGui.QMessageBox.Ignore)
            messageBox.addButton(QtGui.QMessageBox.Cancel)
            
            if messageBox.exec_() == QtGui.QMessageBox.Cancel:
                return
            
            row += 1 + dialog.intraSectionRowSpacing
        
        messageBox = QtGui.QMessageBox()
        messageBox.setWindowTitle('Error')
        
        intervals.sort()
        for i in range(1, len(intervals)):
            if intervals[i][0] < intervals[i-1][1]:
                messageBox.setText('Overlaps among the intervals '
                                            'are not allowed!')
                messageBox.exec_()
                return
        
        if dialog.numOfCategories == 0:
            messageBox.setText('At least one category is required.')
            messageBox.exec_()
            return
        
        return dialogSetting
    
    def idDataActionImpl(self):
        """
        Implementation of action: Data -> ID
        Display the sequences ID's in a dialog
        """
        dialog = QtGui.QDialog()
        dialog.setWindowTitle("Sequence ID's")
        dialog.setMinimumWidth(300)
        dialog.setMinimumHeight(200)
        
        seqIDs = os.linesep.join(self.seqData['id']) if self.seqData else ''
        textEdit = QtGui.QTextEdit()
        textEdit.setPlainText(seqIDs)
        
        vbox = QtGui.QVBoxLayout()
        vbox.addWidget(textEdit)
        
        dialog.setLayout(vbox)
        dialog.exec_()
    
    def seqDataActionImpl(self):
        """
        Implementation of action: Data -> Sequences
        Display the sequences themselves in a dialog
        """
        dialog = QtGui.QDialog()
        dialog.setWindowTitle("Sequences")
        dialog.setMinimumWidth(600)
        dialog.setMinimumHeight(400)
        
        sequences = os.linesep.join(self.seqData['seq']) if self.seqData else ''
        textEdit = QtGui.QTextEdit()
        textEdit.setPlainText(sequences)
        
        vbox = QtGui.QVBoxLayout()
        vbox.addWidget(textEdit)
        
        dialog.setLayout(vbox)
        dialog.exec_()
    
    def lookupDataActionImpl(self):
        """
        Implementation of action: Data -> Lookup
        Display all properties available for a chosen amino acid
        """
        dialog = QtGui.QDialog()
        dialog.setWindowTitle('Amino acids')
        dialog.setFixedWidth(400)
        dialog.setFixedHeight(200)
        
        grid = QtGui.QGridLayout()

        i = 0
        row = 0
        col = 0
        for aa in AA_LIST:            
            aaCheckBox = QtGui.QRadioButton(aa)
            grid.addWidget(aaCheckBox, row, col)
            
            i += 1
            col += 1
            
            if i % 5 == 0:                
                row += 1
                col = 0
        
        continueButton = QtGui.QPushButton('Continue')
        closeButton = QtGui.QPushButton('Close')
        
        continueButton.clicked.connect(self.lookupContinueAction)
        closeButton.clicked.connect(self.lookupCloseAction)
        
        grid.setRowMinimumHeight(row, 20)
        grid.addWidget(continueButton, row+1, 3)
        grid.addWidget(closeButton, row+1, 4)
        
        dialog.setLayout(grid)
        closeButton.setDefault(True)
        
        self.lookupDialog = dialog
        self.lookupDialog.exec_()
    
    def lookupContinueAction(self):
        """Continue to display the actual data for the chosed amino acid"""
        row = 0
        col = 0
        grid = self.lookupDialog.layout()
        
        i = 0
        while i < 20:            
            aaButton = grid.itemAtPosition(row, col).widget()
            if aaButton.isChecked():
                self.showAALookupResult(aaButton.text())
            
            i += 1
            col += 1
            
            if i % 5 == 0:                
                row += 1
                col = 0
    
    def lookupCloseAction(self):
        """Exit looking-up"""
        self.lookupDialog.close()
    
    def showAALookupResult(self, aa):
        """Implementation of data display for a chosen amino acid"""
        dialog = QtGui.QDialog()
        dialog.setWindowTitle('Amino acid: %s' % aa)
        
        properties = sorted(list(self.aaSettings.keys()))
        
        grid = QtGui.QGridLayout()
        
        grid.addWidget(QtGui.QLabel('Property'), 0, 0, 1, 4)
        grid.addWidget(QtGui.QLabel('Value'), 0, 5, 1, 2)
        grid.addWidget(QtGui.QLabel('Category'), 0, 8, 1, 3)
        
        grid.setRowMinimumHeight(1, 10)
        grid.setColumnMinimumWidth(4, 5)
        grid.setColumnMinimumWidth(7, 5)
        
        row = 2
        col = 0
        
        for property in properties:
            
            propertyLabel = QtGui.QLabel(property)
            grid.addWidget(propertyLabel, row, col, 1, 4)
            
            col += 5
            propertyValue = QtGui.QLineEdit(str(self.aaProperties[aa][property]))
            grid.addWidget(propertyValue, row, col, 1, 2)
            
            col += 3
            propertyType = QtGui.QLineEdit(self.aaPropertyTypes[aa][property])
            grid.addWidget(propertyType, row, col, 1, 3)
            
            row += 1
            col = 0
        
        dialog.setLayout(grid)
        dialog.exec_()
    
    def refreshViewActionImpl(self):
        """Implementation of action: View -> Refresh"""
        alignmentLinePositions = []
        for alignmentLine in self.view.alignmentLines:
            alignmentLinePositions.append(alignmentLine.scenePos())
        
        shadowedRegions = []
        shadowColors = []
        for shadowedRegion in self.view.shadowedRegions:
            shadowedRegions.append(shadowedRegion)
            shadowColors.append(self.view.shadowedRegions[shadowedRegion][0])
        
        selectedItems = self.scene.selectedItems()
        selectedItemCoordinates = []
        for eachItem in selectedItems:
            selectedItemCoordinates.append(getItemCoordinates(eachItem))
        
        self.view.scene.clear()
        self.view.alignmentLines = []
        self.view.shadowedRegions = {}
        
        if self.seqData:
            self.view.drawView(self)
            self.lineNumViewActionImpl()
            self.colNumViewActionImpl()
            
            # keep the alignment lines at previous locations in range
            for alignmentLinePos in alignmentLinePositions:
                self.view.drawAlignmentLine(self)
                if alignmentLinePos.x() <= self.view.frameMarginLeft + self.view.frameWidth:
                    self.view.alignmentLines[-1].setPos(alignmentLinePos)
                else:
                    self.view.alignmentLines[-1].setPos(self.view.frameMarginLeft + self.view.frameWidth, 
                                                        alignmentLinePos.y())
            
            # keep the previous region shades in range
            for i in range(len(shadowedRegions)):
                region = shadowedRegions[i]
                color = shadowColors[i]
                
                if region[0] >= self.seqData['maxSeqLen']:
                    continue
                elif region[1] >= self.seqData['maxSeqLen'] - 1:
                    region = (region[0], self.seqData['maxSeqLen'] - 1)
                
                self.view.drawRegionShadow(self, region, color)
            
            # select the previously selected items
            for coordinates in selectedItemCoordinates:
                self.view.alignUnitItems[coordinates[0]][coordinates[1]].setSelected(True)
    
    def propertyViewActionImpl(self):
        """
        Implementation of action: View -> Property
        Users may chose one property to view the alignment
        The property can be either default or customized
        """
        activeChoice = self.propertyViewActionGroup.checkedAction()
        
        if activeChoice == self.identityViewAction:
            self.propertyCurrent = 'identity'
        elif activeChoice == self.sizeViewAction:
            self.propertyCurrent = 'size'
        elif activeChoice == self.chargeViewAction:
            self.propertyCurrent = 'charge'
        elif activeChoice == self.polarityViewAction:
            self.propertyCurrent = 'polarity'
        elif activeChoice == self.hydroViewAction:
            self.propertyCurrent = 'hydropathy'
        elif activeChoice == self.moreViewAction:
            
            allProperties = set(self.aaSettings.keys())
            defaultProperties = set(self.defaultProperties)
            customizedProperties = allProperties - defaultProperties
            
            dialog = QtGui.QDialog()
            dialog.setMinimumWidth(320)
            dialog.setMinimumHeight(200)
            dialog.setWindowTitle('More properties')
            
            grid = QtGui.QGridLayout()
            
            introductionLabel = QtGui.QLabel(
                        'Currently available customized properties:')
            grid.addWidget(introductionLabel, 0, 0, 1, 3)
            
            customizedProperties = sorted(list(customizedProperties))
            buttonGroup = QtGui.QButtonGroup()
            row = 1
            
            for property in customizedProperties:                
                radioButton = QtGui.QRadioButton(property)
                if self.propertyCurrent == property:
                    radioButton.setChecked(True)
                
                grid.addWidget(radioButton, row, 1, 1, 1)
                buttonGroup.addButton(radioButton)                
                row += 1
            
            buttonGroup.buttonClicked.connect(
                                              self.selectionFromMorePropertiesAction)
            
            # when the property does not change, 
            # program would set the action menu to previous state
            if self.propertyCurrent == 'identity':
                self.identityViewAction.setChecked(True)
            elif self.propertyCurrent == 'size':
                self.sizeViewAction.setChecked(True)
            elif self.propertyCurrent == 'charge':
                self.chargeViewAction.setChecked(True)
            elif self.propertyCurrent == 'polarity':
                self.polarityViewAction.setChecked(True)
            elif self.propertyCurrent == 'hydropathy':
                self.hydroViewAction.setChecked(True)
            
            widget = QtGui.QWidget()
            widget.setLayout(grid)
            
            scrollArea = QtGui.QScrollArea()
            scrollArea.setWidget(widget)
            
            hbox = QtGui.QHBoxLayout()
            hbox.addWidget(scrollArea)
            dialog.setLayout(hbox)
            
            dialog.grid = grid
            self.morePropertiesViewDialog = dialog
            self.morePropertiesViewDialog.exec_()
        
        self.refreshViewActionImpl()
    
    def selectionFromMorePropertiesAction(self, btn):
        """Select a property from customized properties"""
        self.propertyCurrent = btn.text().lower()
        self.moreViewAction.setChecked(True)
        self.morePropertiesViewDialog.close()
        self.refreshViewActionImpl()
    
    def colorViewActionImpl(self):
        """
        Implementation of action: View -> Color
        Customize the color settings for current property only
        If you want to setup for other properties, change the property first
        """
        dialog = QtGui.QDialog()
        dialog.setWindowTitle('Color Settings for ' + 
                              self.propertyCurrent.title())
        dialog.setStyleSheet('QWidget { background-color : white}')
        dialog.setFixedWidth(400)
        dialog.setFixedHeight(320)
        
        grid = QtGui.QGridLayout()
        dialog.gridRow = 20
        dialog.gridColumn = 32
        
        saveAsDefaultButton = QtGui.QPushButton('Save as default')
        updateButton = QtGui.QPushButton('Update')
        cancelButton = QtGui.QPushButton('Cancel')
        
        saveAsDefaultButton.clicked.connect(self.saveAsDefaultColorAction)
        updateButton.clicked.connect(self.updateColorAction)
        cancelButton.clicked.connect(self.cancelColorAction)
        
        grid.addWidget(cancelButton, dialog.gridRow-2, dialog.gridColumn-6, 2, 6)
        grid.addWidget(saveAsDefaultButton, dialog.gridRow-2, 1, 2, 8)
        grid.addWidget(updateButton, dialog.gridRow-2, dialog.gridColumn-12, 2, 6)
        
        if self.propertyCurrent == 'identity':            
            dialog.categoryPerRow = 3
            dialog.startRow = 1
            dialog.startColumn = 1
            dialog.categoryWidth = 2
            dialog.colorWidth = 2
            dialog.categoryColorSpacingWidth = 0
            dialog.categoryCategorySpacingWidth = 7
            dialog.rowSpacing = 1
            dialog.gridMinWidth = 10
            dialog.gridMinHeight = 5
        else:
            # the number of categories does not include "undefined"
            nCategories = len(self.propertyColors[self.propertyCurrent].keys()) - 1
            dialog.categoryPerRow = 1
            dialog.startRow = (dialog.gridRow - 2) // 2 - nCategories
            dialog.startColumn = 8
            dialog.categoryWidth = 8
            dialog.colorWidth = 3
            dialog.categoryColorSpacingWidth = 1
            dialog.categoryCategorySpacingWidth = 5
            dialog.rowSpacing = 1
            dialog.gridMinWidth = 10
            dialog.gridMinHeight = 10
        
        for i in range(dialog.gridColumn):
            grid.setColumnMinimumWidth(i, dialog.gridMinWidth)
        
        for i in range(dialog.gridRow):
            grid.setRowMinimumHeight(i, dialog.gridMinHeight)

        categories = sorted(list(self.propertyColors[self.propertyCurrent].keys()))
        if 'undefined' in categories:
            categories.remove('undefined')
        
        i = 0
        row = dialog.startRow
        col = dialog.startColumn
        
        for eachCategory in categories:            
            categoryLabel = QtGui.QLabel(eachCategory)
            grid.addWidget(categoryLabel, row, col, 1, dialog.categoryWidth)
            col += dialog.categoryWidth + dialog.categoryColorSpacingWidth
            
            colorFrame = RFrame(
                    self.propertyColors[self.propertyCurrent][eachCategory])
            grid.addWidget(colorFrame, row, col, 1, dialog.colorWidth)
            col += dialog.colorWidth + dialog.categoryCategorySpacingWidth
            
            i += 1
            if i % dialog.categoryPerRow == 0:                
                col = dialog.startColumn
                row += 1 + dialog.rowSpacing
        
        dialog.setLayout(grid)
        dialog.grid = grid
        
        self.colorConfigDialog = dialog
        self.colorConfigDialog.exec_()
    
    def saveAsDefaultColorAction(self):
        """Save current color settings as default"""
        messageBox = QtGui.QMessageBox()
        messageBox.setWindowTitle('Confirmation')
        messageBox.setText('Are you sure to overwrite default settings?')
        messageBox.addButton(QtGui.QMessageBox.Ok)
        messageBox.addButton(QtGui.QMessageBox.Cancel)
        
        if messageBox.exec_() == QtGui.QMessageBox.Cancel:
            return
        
        propertyColorsUpdate = self.extractColorConfigResult(self.colorConfigDialog)
        self.allSettingsDefault = getSettingsDefault()
        
        if self.propertyCurrent != 'identity' and \
                    self.propertyCurrent not in \
                    self.allSettingsDefault['property type definition'].keys():            
            for aa in self.allSettingsDefault['aa properties'].keys():
                self.allSettingsDefault['aa properties'][aa][self.propertyCurrent] = \
                            self.aaProperties[aa][self.propertyCurrent]
            
            self.allSettingsDefault['property type definition'][self.propertyCurrent] = \
                copy.deepcopy(self.aaSettings[self.propertyCurrent])
            
        elif self.propertyCurrent != 'identity' and \
                    set(self.allSettingsDefault['property type definition']\
                    [self.propertyCurrent].keys()) != \
                    set(self.aaSettings[self.propertyCurrent].keys()):            
            self.allSettingsDefault['property type definition'][self.propertyCurrent] = \
                copy.deepcopy(self.aaSettings[self.propertyCurrent])
        
        self.allSettingsDefault['property color mapping'][self.propertyCurrent] = \
                        propertyColorsUpdate        
        saveSettingsDefault(self.allSettingsDefault)
    
    def updateColorAction(self):
        """Update the colors to current program and view"""
        messageBox = QtGui.QMessageBox()
        messageBox.setWindowTitle('Confirmation')
        messageBox.setText('Are you sure to update current settings?')
        messageBox.addButton(QtGui.QMessageBox.Ok)
        messageBox.addButton(QtGui.QMessageBox.Cancel)
        
        if messageBox.exec_() == QtGui.QMessageBox.Cancel:
            return
            
        propertyColorsUpdate = \
                        self.extractColorConfigResult(self.colorConfigDialog)
        self.propertyColors[self.propertyCurrent] = \
                        copy.deepcopy(propertyColorsUpdate)
        
        self.currentSettingsSaved = False
        self.refreshViewActionImpl()        
        self.colorConfigDialog.close()
    
    def cancelColorAction(self):
        """Cancel setting the colors"""
        self.colorConfigDialog.close()
    
    def extractColorConfigResult(self, dialog):
        """Read and extract the color setting from the dialog"""
        i = 0
        nCategories = len(self.propertyColors[self.propertyCurrent].keys())
        if self.propertyCurrent != 'identity':
            nCategories -= 1
        
        row = dialog.startRow
        col = dialog.startColumn
        propertyColors = {}
        
        while i < nCategories:
            categoryLabel = dialog.grid.itemAtPosition(row, col).widget()
            category = categoryLabel.text()
            col += dialog.categoryWidth + dialog.categoryColorSpacingWidth
            
            colorFrame = dialog.grid.itemAtPosition(row, col).widget()
            color = colorFrame.color
            col += dialog.colorWidth + dialog.categoryCategorySpacingWidth
            
            i += 1
            propertyColors[category] = QtGui.QColor(color)
            
            if i % dialog.categoryPerRow == 0:                
                col = dialog.startColumn
                row += 1 + dialog.rowSpacing
        
        if self.propertyCurrent != 'identity':
            propertyColors['undefined'] = QtGui.QColor(QtCore.Qt.white)
        
        return propertyColors
    
    def zoomInActionImpl(self):
        """Implementation of action: View -> Zoom -> In"""
        if not self.view.scene.items():
            return
        
        self.view.scale(1.25, 1.25)
        self.viewScaleX *= 1.25
        self.viewScaleY *= 1.25
    
    def zoomOutActionImpl(self):
        """Implementation of action: View -> Zoom -> Out"""
        if not self.view.scene.items():
            return
        
        self.view.scale(0.8, 0.8)
        self.viewScaleX *= 0.8
        self.viewScaleY *= 0.8
    
    def zoomDefaultActionImpl(self):
        """Implementation of action: View -> Zoom -> Default"""
        self.view.scale( 1/self.viewScaleX, 1/self.viewScaleY)
        self.viewScaleX = 1
        self.viewScaleY = 1
    
    def lineNumViewActionImpl(self):
        """
        Implementation of action: View -> Line Number
        Toggle to change the visibility of line numbers and left line
        """
        visible = self.lineNumViewAction.isChecked()
        self.view.frameLineLeft.setVisible(visible)
        
        for item in self.view.seqNumItems:
            item.setVisible(visible)
    
    def colNumViewActionImpl(self):
        """
        Implementation of action: View -> Column Number
        Toggle to change the visibility of column numbers and top line
        """
        visible = self.colNumViewAction.isChecked()
        self.view.frameLineTop.setVisible(visible)
        
        for item in self.view.colNumItems:
            item.setVisible(visible)
    
    def aboutAboutActionImpl(self):
        
        dialog = QtGui.QDialog()
        dialog.setWindowTitle('About %s' % self.programName)
        dialog.setFixedWidth(400)
        dialog.setFixedHeight(300)
        
        vbox = QtGui.QVBoxLayout()        
        vbox.addStretch(1)
        
        pixmap = QtGui.QPixmap('./about.png')
        if not pixmap.isNull():
            pixmap = pixmap.scaled(200, 70)
            hbox = QtGui.QHBoxLayout()
            label = QtGui.QLabel()
            label.setPixmap(pixmap)
            hbox.addWidget(label)
            hbox.setAlignment(QtCore.Qt.AlignCenter)
            vbox.addLayout(hbox)
            vbox.addStretch(1)
        
        labels = ['Program', 'Version', 'License', 'Author', 
                    'Organization', 'Contact', 'Weblink']
        strings = [self.programName, self.version, self.license, self.author, 
                   self.organization, self.contact, self.weblink]
        
        nLabel = len(labels)
        vbox1 = QtGui.QVBoxLayout()
        vbox2 = QtGui.QVBoxLayout()
        for i in range(nLabel):
            vbox1.addWidget(QtGui.QLabel('%s:' % labels[i]))
            vbox2.addWidget(QtGui.QLabel('%s' % strings[i]))
        
        hbox = QtGui.QHBoxLayout()
        hbox.addStretch(1)
        hbox.addLayout(vbox1)
        hbox.addLayout(vbox2)
        hbox.addStretch(1)

        vbox.addLayout(hbox)
        vbox.addStretch(1)
        
        dialog.setLayout(vbox)
        self.aboutPageDialog = dialog
        self.aboutPageDialog.exec_()
    
    def manualAboutActionImpl(self):
        
        new = 2
        webbrowser.open('http://code.google.com/p/'
                        'ccseek-protein-alignment/downloads/list', new=new)
